package com.atguigu.gulimall.product.web;

import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author lirl
 * @date 2021/2/17 16:02
 */
@Slf4j
@Controller
public class IndexController {
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private RedissonClient redisson;
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 由于访问首页时就要加载一级目录,所以我们需要在加载首页时获取该数据
     * @param model
     * @return
     */
    @GetMapping({"/", "index.html"})
    public String getIndex(Model model) {
        //获取所有的一级分类
        List<CategoryEntity> catagories = categoryService.getLevel1Categorys();
        model.addAttribute("categorys", catagories);
        return "index";
    }

    /**
     * 三级分类的数据
     * @return
     */
    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map<String, List<Catelog2Vo>> getCatelogJson(){
        Map<String, List<Catelog2Vo>> catelogJson = categoryService.getCatelogJson();
        return catelogJson;
    }
    /**
     * 测试redisson锁
     * @return
     */
    @ResponseBody
    @GetMapping("/index")
    public String  hello(){
        //1.获取一把锁，可重入锁 R锁
        RLock lock = redisson.getLock("my-lock");
        //2.加锁
       // lock.lock();//阻塞式等待，并不是之前使用redis实现锁时的自旋式等待。默认加的锁失效时间都是30s
        //1)锁的自动续期，如果业务超长，运行期间会自动给锁加上30s的失效时间。不用担心业务时间超长，锁到期后被自动删除
        //2)加锁的业务只要运行完成，就不会给当前锁续期，即使不手动解锁，锁默认在30s后自动删除

        //第二种加锁方式，10s自动解锁，自动解锁的时间一定要大于业务处理时间。
        //问题：这种锁在锁到期了以后不会进行锁的续期操作。
        //原因是：1.如果传递了锁的超时时间，就发送给redis执行脚本进行占锁，默认超时时间就是我们指定的时间
        //       2.如果没有指定超时的时间，就默认使用30*1000【LockWatchDogTimeout看门狗的默认时间】
//                 只要占锁成功，就会启动一个定时任务【重新给锁设置超时时间】这就是看门狗
//                【LockWatchDogTimeout看门狗的默认时间】/3，为10s进行续期操作
        lock.lock(10,TimeUnit.SECONDS );
        try {
        //具体业务
            System.out.println(">>>>>加锁成功，执行业务"+Thread.currentThread().getId());
            Thread.sleep(30000);

        }catch (Exception e){

        }finally {
            //3.解锁
            System.out.println(">>>>>释放锁"+Thread.currentThread().getId());
            lock.unlock();

        }
           return "hello";
    }

    /*  小结：

         读+读：相当于无锁，并发读，只会在redis中记录，所有当前的读锁，都会同时加锁成功
         写+读：等待写锁释放；
         写+写：阻塞方式
         读+写：写锁等待读锁释放，才能加锁
         所以只要存在写操作，不论前面是或后面执行的是读或写操作，都会阻塞*/

    /**
     * 读写锁
     * 保证一定能够读取到最新的数据，修改期间，写锁是一个排他锁（互斥锁），读锁是一个共享锁，写锁没释放读就必须等待。
     * @return
     */
    @GetMapping("/write")
    @ResponseBody
    public String writeValue(){
        RReadWriteLock writeLock=redisson.getReadWriteLock("rw-loc");
        String uuid = null;
        RLock lock = writeLock.writeLock();
        lock.lock();
        try {
            log.info("写锁加锁成功");
            uuid = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set("writeValue",uuid);
            Thread.sleep(30000);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
            log.info("写锁释放");
        }
        return uuid;
    }

    /**
     * 读锁
     * @return
     */
    @GetMapping("/read")
    @ResponseBody
    public String redValue(){
        String uuid = null;
        RReadWriteLock readLock=redisson.getReadWriteLock("rw-loc");
        RLock lock = readLock.readLock();
        lock.lock();
        try {
            log.info("读锁加锁成功");
            uuid = redisTemplate.opsForValue().get("writeValue");
            Thread.sleep(30000);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
            log.info("读锁释放");
        }
        return uuid;


    }
    @GetMapping("/park")
    @ResponseBody
    @SneakyThrows
    /**
     * 车库停车
     * 3车位
     * 信号量也用作分布式限流
     * 提前在redis中设置key=park value = 3代表3个车位
     */
    public String park(){
        RSemaphore park = redisson.getSemaphore("park");
       // park.acquire();//阻塞似等待，获取一个信号获取一个值。占一个车位
        boolean b = park.tryAcquire();//不会等待，直接返回获取的结果
        if(b){
            //执行业务
        }else{
            return "error";
        }
        return "ok=>"+b;
    }

    /**
     * 释放车位
     * @return
     */
    @GetMapping("/go")
    @ResponseBody
    public String go(){
        RSemaphore park = redisson.getSemaphore("park");
        park.release();//释放车位，park的值就会加一。
//        Semaphore  juc包
        return "ok";

    }

    /**
     * 闭锁
     * 例子：锁门，五个班的人全走完了才可以锁门
     * CountDownLatch强调的是一个线程（或多个）需要等待另外的n个线程干完某件事情之后才能继续执行。
     *
     */
    @ResponseBody
    @GetMapping("/lockDoor")
    public String lockDoor() throws InterruptedException {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        // 设置这里有5个人,redis 里的值就会是5
        door.trySetCount(5);
        door.await();//等待闭锁都完成

        return "5个人全部通过了...";
    }

    @ResponseBody
    @GetMapping("/go/{id}")
    public String go(@PathVariable("id") Long id) throws InterruptedException {

        RCountDownLatch door = redisson.getCountDownLatch("door");
        // 每访问一次相当于出去一个人，计数减一
        door.countDown();
//        CountDownLatch    juc包下的闭锁
        return id + "班的人都走了";
    }


}
